/*
**    Copyright (C) 2003-2011 Institute for Systems Biology
**                            Seattle, Washington, USA.
**
**    This library is free software; you can redistribute it and/or
**    modify it under the terms of the GNU Lesser General Public
**    License as published by the Free Software Foundation; either
**    version 2.1 of the License, or (at your option) any later version.
**
**    This library is distributed in the hope that it will be useful,
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
**    Lesser General Public License for more details.
**
**    You should have received a copy of the GNU Lesser General Public
**    License along with this library; if not, write to the Free Software
**    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/

package org.systemsbiology.util;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import static org.systemsbiology.util.ReservedCharacters.*;

/**
 * This utility class provides several common {@link InputStream} operations
 *
 *  These operations are for a limited subset of ASCII parsing operations that do not need a proper (Unicode)
 *  representation, and indeed provide no such facility in order to repudiate binary data. If you require these
 *  operations in a better-supported, format-agnostic fashion capable of parsing both Unicode and ASCII, consider
 *  Java's facilities to do this, through classes such as {@link java.io.BufferedReader} or just plain
 *  {@link java.io.Reader}.
 * @author trobinso
 */
public class InputStreamUtil {

	/**
	* InputStreamUtil is not an instantiable class, so the constructor is blocked.
	*/
	@Deprecated
    private InputStreamUtil(){
		throw new UnsupportedOperationException(
                "InputStreamUtil is a static library that should not be instantiated directly.");
	}

    /**
     * Read until we hit the end of the current line, returning a String representation
     *
     *  See {@link #skipline} for the gory details about how we parse line terminators. This representation preserves
     *  the trailing line separator or EOF at the end of each line. This can be removed via {@link String#trim} or
     *  equivalent.
     * @return {@link String} representing this line from input
     * @param is {@link InputStream} passed by the caller
     * @throws java.io.IOException if reading from the stream fails
     * @see #skipline
     */
    public static String readline(InputStream is) throws java.io.IOException {

        // Initialize our return string
        StringBuffer buf = new StringBuffer();

        // Read until a terminator or EOF are reached
        while (true) {
            char last = read(is);
            buf.append(String.valueOf(last));
            if (last == CARRIAGE_RETURN ||
                last == LINE_FEED ||
                last == RECORD_SEPARATOR ||
                last == EOF) {
                break;
            }
        }

        // Return it
        return buf.toString();
    }

    /**
     * Skip the current line of input
     *
     *  Since we view our file through a byte-sized lens, we read forward until we hit one of the standard ASCII line
     *  terminators (CR, CRLF, LF, RS). We should never be given Unicode input, but if we are for some reason, this
     *  method will NOT work as expected.
     *
     *  Because CRLF is a digraph in the context of generic handling of line terminators, it requires special handling.
     *  This results in the following caveat: skipline() must be called *twice* to handle CRLF, or should otherwise
     *  ignore newline characters that occur as the first character after a line break.
     * @param is {@link InputStream} passed by the caller
     * @throws java.io.IOException if reading from the stream fails
     * @see <a href="https://secure.wikimedia.org/wikipedia/en/wiki/Newline">Wikipedia: Newline</a>
     */
    public static void skipline(InputStream is) throws java.io.IOException {

        // Read until a terminator or EOF are reached
        while (true) {
            char last = read(is);
            if (last == CARRIAGE_RETURN ||
                last == LINE_FEED ||
                last == RECORD_SEPARATOR ||
                last == EOF) {
                break;
            }
        }
    }

    /**
     * Read a byte from the stream, as (ASCII) char
     *
     *  This method makes use of a scope-narrowing operation guaranteed by {@link java.io.InputStream#read}.
     *  Normally, blind boxing of int as char should be flatly discouraged.
     *
     *  As a result of the scope-narrowing operation described above, a return of -1 (EOF) will be promoted to 0xffffu.
     *  This is intentional, allowing us to use 0xffffu as a sentinel value indicating the end of input.
     * @param is {@link InputStream} passed by the caller
     * @return char representation of the current byte read from the stream, or 0xffffu for EOF
     * @throws java.io.IOException if reading from the stream fails
     */
    public static char read(InputStream is) throws java.io.IOException {
        return (char)is.read();
    }

    /**
     * Checks to see if a given character falls out of the printable range of ASCII input
     * @param c char given for value testing
     * @return boolean signaling if this character appears to be binary data instead of a printable ASCII character
     */
    public static boolean smellsLikeBinaryData(char c) {
        return (c < 0x40 || c > 0x7E);
    }

    /**
     * Counts the number of lines in a file. Significantly more efficient than naive use of readLine.
     * In many cases, this is more appropriate than cacheing the file merely because the number of lines in the file
     * is required from the outset.
     * @param filename File to open and scan. The file handle will be released before the function ends.
     * @return The number of lines in the file.
     * @throws IOException if the underlying file does.
     */
    public static long countLines(String filename) throws IOException {
    InputStream is = /*new BufferedInputStream(*/new FileInputStream(filename)/*)*/;
    try {
        byte[] c = new byte[8192];
        long count = 0;
        int readChars = 0;
        boolean endedNewline = false;
        while ((readChars = is.read(c)) != -1) {
            for (int i = 0; i < readChars; ++i) {
                if (c[i] == '\n')
                    ++count;
            }
            endedNewline = c[readChars-1] == '\n';
        }
        return endedNewline ? count : count+1;
    } finally {
        is.close();
    }
}
}
